﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.DBIBase
{
    /// <summary>
    /// 数据库访问对象创建工厂
    /// </summary>
    public class DBFactoryManager
    {
        /// <summary>
        /// 数据库工厂集合[key:数据库访问工厂类型(Type),value:数据库访问工厂实例(DBFactoryBase)]
        /// </summary>
        private static readonly ConcurrentDictionary<Type, IDBFactory> _dbFactoryDic = new ConcurrentDictionary<Type, IDBFactory>();
        /// <summary>
        /// [key:dbid;value:IDBFactory]
        /// </summary>
        private static readonly ConcurrentDictionary<int, IDBFactory> _dbFactoryDic2 = new ConcurrentDictionary<int, IDBFactory>();
        private static readonly object _dbFactoryDicLock = new object();


        /// <summary>
        /// 初始化
        /// </summary>
        public static void Init()
        {
            var assemblyArr = AppDomain.CurrentDomain.GetAssemblies();
            Dictionary<string, Assembly> assembliyDic = new Dictionary<string, Assembly>();
            foreach (var assemblyItem in assemblyArr)
            {
                assembliyDic[assemblyItem.GetName().Name] = assemblyItem;
            }

            Assembly currentAssembly = typeof(DBFactoryManager).Assembly;
            var ignoreAssemblyNames = new List<string>();
            ignoreAssemblyNames.Add(currentAssembly.GetName().Name);
            ignoreAssemblyNames.Add(typeof(UtilZ.Dotnet.Ex.Base.ObjectEx).Assembly.GetName().Name);
            ignoreAssemblyNames.AddRange(assembliyDic.Keys);

            //string dbPluginRootFullDir = Path.Combine(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "DBPlugins");
            string dbPluginRootFullDir = PathEx.GetEntryAssemblyDirectory();
            Type idbFactoryType = typeof(IDBFactory);
            string[] dllFullPathArr = Directory.GetFiles(dbPluginRootFullDir, "UtilZ.Dotnet.DB*.dll", SearchOption.AllDirectories);
            Assembly assembly;

            foreach (var dllFullPath in dllFullPathArr)
            {
                try
                {
                    AssemblyName an = AssemblyName.GetAssemblyName(dllFullPath);
                    if (ignoreAssemblyNames.Contains(an.Name))
                    {
                        continue;
                    }

                    if (assembliyDic.ContainsKey(an.Name))
                    {
                        assembly = assembliyDic[an.Name];
                    }
                    else
                    {
                        assembly = Assembly.LoadFrom(dllFullPath);
                    }

                    ignoreAssemblyNames.Add(assembly.GetName().Name);
                    PrimitiveLoadDBPlugin(idbFactoryType, assembly);
                }
                catch (Exception ex)
                {
                    ZLoger.Error($"加载{dllFullPath}异常", ex);
                }
            }

            PluginLoadCompleted();
        }


        private static void PrimitiveLoadDBPlugin(Type idbFactoryType, Assembly assembly)
        {
            foreach (Type type in assembly.ExportedTypes)
            {
                if (!type.IsClass ||
                    type.IsAbstract ||
                    type.GetInterface(idbFactoryType.FullName) == null ||
                    !type.GetConstructors().Where(c => { return c.GetParameters().Length == 0; }).Any())
                {
                    //不是类或抽象类或没有实现IDBInteraction接口或没有无参构造函数,忽略
                    continue;
                }

                var dbFactory = Activator.CreateInstance(type) as IDBFactory;
                if (dbFactory == null)
                {
                    continue;
                }

                dbFactory.AttatchEFConfig();
                _dbFactoryDic[type] = dbFactory;
            }
        }

        /// <summary>
        /// 插件加载完成
        /// </summary>
        private static void PluginLoadCompleted()
        {
            //注:插件加载完成后,在此实例化一次EFDbContext,.NET的一个诡异BUG,应用域中已加载程序集却要报找不着,
            //执行下面代码手动加载EF依赖程序集。当该BUG解决后,删除本函数以及TestEFDbContextDbConnectionInfo类
            if (_dbFactoryDic.Count == 0)
            {
                return;
            }

            //var enable = Ex.Base.AssemblyEx.Enable;
            //try
            //{
            //    Ex.Base.AssemblyEx.Enable = true;
            //    var dbFactory = _dbFactoryDic.Values.ElementAt(0);
            //    using (var efDbContext = new EF.EFDbContext(new TestEFDbContextDbConnectionInfo(dbFactory.GetDBInteraction().GetProviderFactory().CreateConnection()), null))
            //    {

            //    }
            //}
            //finally
            //{
            //    Ex.Base.AssemblyEx.Enable = enable;
            //}
        }

        /// <summary>
        /// 手动加载数据库插件
        /// </summary>
        /// <param name="pluginAssemblyPath">数据库插件Assembly路径</param>
        internal static void LoadDBPlugin(string pluginAssemblyPath)
        {
            if (string.IsNullOrWhiteSpace(pluginAssemblyPath))
            {
                throw new ArgumentNullException(nameof(pluginAssemblyPath));
            }

            if (string.IsNullOrWhiteSpace(Path.GetPathRoot(pluginAssemblyPath)))
            {
                pluginAssemblyPath = PathEx.GetFullPath(pluginAssemblyPath);
            }

            if (!File.Exists(pluginAssemblyPath))
            {
                throw new FileNotFoundException("数据库插件程序集不存在", pluginAssemblyPath);
            }

            AssemblyName an = AssemblyName.GetAssemblyName(pluginAssemblyPath);
            Dictionary<string, Assembly> assembliyDic = AppDomain.CurrentDomain.GetAssemblies().ToDictionary(p => { return p.GetName().Name; });
            Assembly assembly;
            if (assembliyDic.ContainsKey(an.Name))
            {
                assembly = assembliyDic[an.Name];
            }
            else
            {
                assembly = Assembly.LoadFrom(pluginAssemblyPath);
            }

            PrimitiveLoadDBPlugin(typeof(IDBFactory), assembly);
            PluginLoadCompleted();
        }

        /// <summary>
        /// 获取数据库访问工厂实例
        /// </summary>
        /// <param name="config">数据库配置</param>
        /// <returns>数据库访问工厂实例</returns>
        public static IDBFactory GetDBFactory(DatabaseConfig config)
        {
            return GetDBFactoryByConfig(config);
        }


        /// <summary>
        /// 根据数据库配置获取数据库访问工厂实例
        /// </summary>
        /// <param name="config">数据库配置</param>
        /// <returns>数据库访问工厂实例</returns>
        private static IDBFactory GetDBFactoryByConfig(DatabaseConfig config)
        {
            int dbid = config.DBID;
            IDBFactory dbFactory;
            if (_dbFactoryDic2.TryGetValue(dbid, out dbFactory))
            {
                return dbFactory;
            }

            lock (_dbFactoryDicLock)
            {
                if (_dbFactoryDic2.TryGetValue(dbid, out dbFactory))
                {
                    return dbFactory;
                }

                //Type type = GetDBFactoryTypeByAssemblyByName(dbFactoryName);
                Type type = TypeEx.GetType(config.DBFactory);
                if (type == null)
                {
                    throw new ApplicationException(string.Format("获取名称为{0}数据库创建工厂实例失败", config.DBFactory));
                }

                dbFactory = Activator.CreateInstance(type) as IDBFactory;
                if (dbFactory == null)
                {
                    throw new ArgumentException(string.Format("创建名称为{0}数据库创建工厂实例失败", config.DBFactory));
                }

                _dbFactoryDic[type] = dbFactory;
                _dbFactoryDic2[dbid] = dbFactory;

                //注：此行废代码是为了在未配置读写连接数时，通过创建一个连接来加载数据库对应的程序集
                //dbFactory.AttatchEFConfig();
                if (config.ReadConCount < DBConstant.ReadConCount && config.WriteConCount < DBConstant.WriteConCount)
                {
                    using (var con = dbFactory.GetDBInteraction().GetProviderFactory().CreateConnection())
                    {

                    }
                }

                return dbFactory;
            }
        }



        private static Type GetDBFactoryTypeByAssemblyByName(string dbFactoryName)
        {
            string[] strArr = dbFactoryName.Split(',');
            if (strArr.Length < 2)
            {
                throw new NotSupportedException($"不支持的程序集格式\"{dbFactoryName}\"");
            }

            string typeName = strArr[0].Trim();
            Assembly assembly = AssemblyEx.FindAssembly(typeName);
            Type type;
            if (assembly != null)
            {
                type = assembly.GetType(typeName, false, true);
                if (type != null)
                {
                    return type;
                }
            }

            string dbPluginDir;
            if (strArr[1].Contains(PathEx.LeftSlash) || strArr[1].Contains(PathEx.RightSlash))
            {
                string filePath = PathEx.GetFullPath(strArr[1]);
                DirectoryInfo dbPluginRootDirInfo = Directory.GetParent(filePath);
#if DEBUG
                dbPluginDir = Path.Combine(dbPluginRootDirInfo.FullName, "Debug");
#else
                dbPluginDir = Path.Combine(dbPluginRootDirInfo.FullName, "Release");
#endif
            }
            else
            {
                dbPluginDir = PathEx.GetEntryAssemblyDirectory();
            }

            string fileName = Path.GetFileName(strArr[1]);
            string[] assemblyPaths = Directory.GetFiles(dbPluginDir, $"{fileName}.dll", SearchOption.AllDirectories);
            if (assemblyPaths == null || assemblyPaths.Length == 0)
            {
                throw new ArgumentException($"找不到对应的DB程序集\"{dbFactoryName}\"");
            }

            foreach (var assemblyPath in assemblyPaths)
            {
                try
                {
                    assembly = Assembly.LoadFile(assemblyPath);
                    type = assembly.GetType(typeName, false, true);
                    if (type != null)
                    {
                        return type;
                    }
                }
                catch (Exception ex)
                {
                    ZLoger.Warn(ex);
                }
            }

            return null;
        }



        /// <summary>
        /// 获取Sql字段值格式化对象
        /// </summary>
        /// <param name="config">数据库配置</param>
        /// <returns>Sql字段值格式化对象</returns>
        public static ISqlFieldValueFormator GetSqlFieldValueFormator(DatabaseConfig config)
        {
            return GetDBFactoryByConfig(config).GetDBInteraction().SqlFieldValueFormator;
        }
    }
}
